﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace LicenseData
{
	public class LicenseDictionary
	{
		public const string LicenseDataFile = "Net Manager License Data.txt";
		public const string LicenseDataBackupFile = "Net Manager License Data Backup.txt";
		private const string SqlUpdateFile = "license_updates{0}.sql";
		private const int MaxLinesPerFile = 70000;

		private Dictionary<string, LicenseDetails> _dictionary;
		private LinkedList<LicenseDetails> _unchangedLicenses;
		private LinkedList<LicenseDetails> _updatedLicenses;
		private ReaderWriterLockSlim _lock;
		private int _totalLines;
		private int _totalSqlFiles;
		private bool _isUpdated;

		private LicenseDictionary()
		{
			_dictionary = new Dictionary<string, LicenseDetails>();
			_lock = new ReaderWriterLockSlim();
		}

		public DateTimeOffset UpdateTime
		{
			get;
			private set;
		}

		public int TotalUpdatedLicenses
		{
			get
			{
				if ( _updatedLicenses != null )
					return _updatedLicenses.Count;
				else
					return 0;
			}
		}

		public int TotalUnchangedLicenses
		{
			get
			{
				if ( _unchangedLicenses != null )
					return _unchangedLicenses.Count;
				else
					return 0;
			}
		}

		public int TotalRemovedLicenses
		{
			get
			{
				return _dictionary.Count;
			}
		}

		public int Count
		{
			get
			{
				return _dictionary.Count;
			}
		}

		public int TotalSqlFilesCreated
		{
			get
			{
				return _totalSqlFiles;
			}
		}

		public void UpdateLicenseDataFile()
		{
			DateTimeOffset
				now = DateTimeOffset.UtcNow;
			StreamWriter
				sqlWriter = null;

			if ( _isUpdated )
			{
				try
				{
					_lock.EnterReadLock();

					//////////////////////////////////////////////
					// back up the file before trying to update it
					//////////////////////////////////////////////
					if ( File.Exists( LicenseDataFile ) && !File.Exists( LicenseDataBackupFile ) )
						File.Move( LicenseDataFile, LicenseDataBackupFile );

					////////////////////////////////////////////////
					// open the data file and write the updated data
					////////////////////////////////////////////////
					using ( StreamWriter writer = File.CreateText( LicenseDataFile ) )
					{
						if ( _unchangedLicenses != null )
						{
							writer.WriteLine( now );
							foreach ( LicenseDetails item in _unchangedLicenses )
							{
								writer.WriteLine( item );
							}
							foreach ( LicenseDetails item in _updatedLicenses )
							{
								writer.WriteLine( item );
							}
						}
						else
						{
							writer.WriteLine( UpdateTime );
							foreach ( LicenseDetails item in _dictionary.Values )
							{
								writer.WriteLine( item );
							}
						}
					}

					///////////////////////////////////////////////////
					// file save was successful, remove the backup copy
					///////////////////////////////////////////////////
					if ( File.Exists( LicenseDataBackupFile ) )
						File.Delete( LicenseDataBackupFile );

					///////////////////////////////////////////////////////////////////////////////////////////////
					// if license updates came from the actual FCC data, create a series of SQL files to upload the
					// license changes to the DB for later download by the Net Manager application
					///////////////////////////////////////////////////////////////////////////////////////////////
					if ( _updatedLicenses != null && ( _updatedLicenses.Count > 0 || _dictionary.Count > 0 ) && _unchangedLicenses.Count > 0 )
					{
						foreach ( LicenseDetails item in _dictionary.Values )
						{
							OpenSqlWriter( ref sqlWriter );
							_totalLines += item.WriteLicenseUpdateSql( sqlWriter, now, true );
						}
						foreach ( LicenseDetails item in _updatedLicenses )
						{
							OpenSqlWriter( ref sqlWriter );
							_totalLines += item.WriteLicenseUpdateSql( sqlWriter, now, false );
						}
						sqlWriter.Dispose();
					}

					_isUpdated = false;
				}
				finally
				{
					_lock.ExitReadLock();
				}
			}
		}

		public void UpdateLicense( LicenseDetails details )
		{
			LicenseDetails
				existingEntry;

			_isUpdated = true;

			_lock.EnterWriteLock();

			if ( details.UpdateTime.HasValue )
			{
				if ( details.IsRemoved )
					_dictionary.Remove( details.RosterId );
				else
					_dictionary[ details.RosterId ] = details;

				if ( details.UpdateTime.Value > UpdateTime )
					UpdateTime = details.UpdateTime.Value;
			}
			else
			{
				if ( _unchangedLicenses == null )
				{
					_unchangedLicenses = new LinkedList<LicenseDetails>();
					_updatedLicenses = new LinkedList<LicenseDetails>();
				}

				if ( _dictionary.TryGetValue( details.RosterId, out existingEntry ) )
				{
					if ( !details.Equals( existingEntry ) )
						_updatedLicenses.AddLast( details );
					else
						_unchangedLicenses.AddLast( details );

					_dictionary.Remove( details.RosterId );
				}
				else
				{
					_updatedLicenses.AddLast( details );
				}
			}

			_lock.ExitWriteLock();
		}

		public bool IsLicenseDefined( string rosterId )
		{
			bool
				isDefined;

			_lock.EnterReadLock();
			isDefined = _dictionary.ContainsKey( rosterId );
			_lock.ExitReadLock();

			return isDefined;
		}

		public LicenseDetails GetDetails( string rosterId )
		{
			LicenseDetails
				details;

			_lock.EnterReadLock();
			_dictionary.TryGetValue( rosterId, out details );
			_lock.ExitReadLock();

			return details;
		}

		private void OpenSqlWriter( ref StreamWriter sqlWriter )
		{
			if ( _totalLines % MaxLinesPerFile == 0 )
			{
				if ( sqlWriter != null )
					sqlWriter.Dispose();
				sqlWriter = File.CreateText( string.Format( SqlUpdateFile, _totalSqlFiles++ ) );
			}
		}

		public static LicenseDictionary GetLicenseDictionary()
		{
			string
				input;
			LicenseDictionary
				licenses = new LicenseDictionary();
			LicenseDetails
				licenseDetails;

			/////////////////////////////////////////////////////////////////////////////
			// if the last file save failed and left the backup file in place, remove the
			// corrupt file and restore the backup
			/////////////////////////////////////////////////////////////////////////////
			if ( File.Exists( LicenseDataBackupFile ) )
			{
				if ( File.Exists( LicenseDataFile ) )
					File.Delete( LicenseDataFile );
				File.Move( LicenseDataBackupFile, LicenseDataFile );
			}

			if ( File.Exists( LicenseDataFile ) )
			{
				using ( StreamReader reader = File.OpenText( LicenseDataFile ) )
				{
					licenses.UpdateTime = DateTimeOffset.Parse( reader.ReadLine() );
					while ( ( input = reader.ReadLine() ) != null )
					{
						licenseDetails = new LicenseDetails( input );
						licenses._dictionary[ licenseDetails.RosterId ] = licenseDetails;
					}
				}
			}
			else
				licenses.UpdateTime = DateTimeOffset.UtcNow;

			return licenses;
		}
	}
}
